package com.disruptor.demo.handler;

import com.disruptor.demo.event.SeckillOrderEvent;
import com.lmax.disruptor.EventHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 库存校验与扣减处理器：Disruptor消费链的第一个核心处理器
 * 定位：秒杀流程的"第一道防线"，负责库存安全性校验与原子扣减，是防超卖的关键环节
 * 核心职责：
 * 1. 基于原子操作实现高并发下的库存扣减（避免超卖）
 * 2. 批量日志输出（减少单条日志IO开销，适配高吞吐场景）
 * 3. 记录库存耗尽状态与时间（供测试/监控判断秒杀结束时机）
 * 4. 提供成功订单数、剩余库存等统计数据（支撑最终结果校验）
 */
@Slf4j
@Component
public class StockCheckHandler implements EventHandler<SeckillOrderEvent> {
    // ========================== 核心变量定义（线程安全设计） ==========================
    /**
     * 剩余库存计数器
     * 原子类选择原因：高并发下多线程同时扣减库存，AtomicInteger的CAS操作确保扣减原子性（防超卖）
     * 初始化来源：构造器注入配置文件中的"最大库存"（seckill.max-stock）
     */
    private final AtomicInteger remainingStock;

    /**
     * 累计成功订单数
     * 作用：统计通过库存校验的订单总量，需与"最大库存"匹配（验证无超卖）
     * 线程安全：AtomicInteger确保多线程统计无计数偏差
     */
    private final AtomicInteger successOrderCount = new AtomicInteger(0);

    /**
     * 库存是否耗尽的标记
     * 原子布尔值作用：确保"库存耗尽"状态仅被标记一次（避免重复打印耗尽日志）
     * 初始状态：false（库存未耗尽）
     */
    private final AtomicBoolean stockExhausted = new AtomicBoolean(false);

    /**
     * 当前批次成功订单数
     * 用途：批量日志计数，达到阈值后打印汇总日志（减少单条日志的IO开销）
     */
    private final AtomicInteger batchSuccessCount = new AtomicInteger(0);

    /**
     * 库存耗尽时间戳（毫秒）
     * 作用：记录库存被抢完的时间，供测试类计算"秒杀完成耗时"
     */
    private long stockExhaustTime;

    /**
     * 批量日志阈值：每成功处理1000条订单，或达到事件批次末尾时，打印一次汇总日志
     * 阈值设计逻辑：
     * - 过小：日志量激增，占用磁盘IO和CPU资源
     * - 过大：日志延迟，无法实时监控库存扣减进度
     * 1000条/批为高吞吐场景的平衡值
     */
    private static final int BATCH_LOG_THRESHOLD = 2000;

    // ========================== 构造器（库存初始化） ==========================

    /**
     * 构造器：初始化库存并打印初始化日志
     * 核心逻辑：从配置文件注入"最大库存"，确保启动时库存状态可追溯
     *
     * @param maxStock 秒杀商品最大库存（配置来源：application.yml的seckill.max-stock，默认100）
     *                 生产场景注意：实际应从MySQL/Redis加载实时库存，此处为单实例简化
     */
    public StockCheckHandler(@Value("${seckill.max-stock:100}") int maxStock) {
        this.remainingStock = new AtomicInteger(maxStock);
        // 初始化日志：启动时确认库存配置是否正确，避免因配置错误导致秒杀异常
        log.info("【库存初始化】秒杀商品最大库存：{}，初始剩余库存：{}", maxStock, remainingStock.get());
    }

    // ========================== Disruptor核心事件处理方法 ==========================

    /**
     * 处理订单事件的核心逻辑：库存校验→原子扣减→批量日志
     * 调用时机：当生产者发布订单事件后，Disruptor自动触发该方法（单线程/多线程取决于配置）
     *
     * @param event      待处理的秒杀订单事件（包含用户ID、购买数量等关键信息）
     * @param sequence   事件在RingBuffer中的唯一序列号（用于定位事件位置，排查问题）
     * @param endOfBatch 是否为当前批次的最后一个事件（确保末尾小批量事件也能打印日志）
     */
    @Override
    public void onEvent(SeckillOrderEvent event, long sequence, boolean endOfBatch) throws Exception {
        // 1. 更新事件状态：标记为"STOCK_CHECK"，明确当前处于库存校验阶段（便于追踪事件流转）
        event.setProcessStatus("STOCK_CHECK");
        String orderId = event.getOrderId(); // 订单唯一标识，用于日志追踪
        int buyCount = event.getBuyCount();  // 用户购买数量（秒杀场景通常限制为1）

        // 2. CAS循环重试：处理并发库存扣减冲突（核心防超卖逻辑）
        // 循环原因：多线程同时扣减时，CAS可能失败，需重试直到成功或确认库存不足
        while (true) {
            // 2.1 读取当前库存（AtomicInteger的get()方法保证可见性）
            int currentStock = remainingStock.get();

            // 2.2 库存不足判断：当前库存 < 购买数量，直接标记失败并返回
            if (currentStock < buyCount) {
                event.setErrorMsg("库存不足了"); // 记录失败原因，供后续通知使用

                // 仅第一次库存耗尽时打印日志（避免重复刷屏）
                // compareAndSet确保多线程下"耗尽标记"仅被设置一次
                if (stockExhausted.compareAndSet(false, true)) {
                    // 日志包含"耗尽时间"（相对于服务启动），便于计算"秒杀完成耗时"
                    log.info("【库存耗尽】当前剩余库存：{}，累计成功订单：{}", currentStock, successOrderCount.get());
                }

                // 不打印单条失败日志：避免10万级请求导致日志爆炸，最终通过测试类统计总失败数
                return;
            }

            // 2.3 原子扣减库存：CAS操作（Compare-And-Swap）
            // 逻辑：仅当"当前库存 == 预期库存"时，才扣减为"currentStock - buyCount"
            boolean casSuccess = remainingStock.compareAndSet(currentStock, currentStock - buyCount);
            if (casSuccess) {
                // 2.4 扣减成功：更新统计计数器
                successOrderCount.incrementAndGet(); // 累计成功数+1
                batchSuccessCount.incrementAndGet();  // 批次成功数+1

                // 2.5 批量打印日志：达到阈值或批次末尾时触发
                if (batchSuccessCount.get() >= BATCH_LOG_THRESHOLD) {
                    log.info("【库存校验批次汇总】本批次成功：{}条，累计成功：{}条，剩余库存：{}",
                            batchSuccessCount.get(), successOrderCount.get(), remainingStock.get());
                    batchSuccessCount.set(0); // 重置批次计数，准备下一批
                }

                break; // 扣减成功，退出重试循环
            }

            // 2.6 CAS失败：说明其他线程已修改库存，无需日志（重试即可）
            // 此处不打印debug日志：避免高并发下日志量激增，影响性能
        }
    }

    // ========================== 统计数据对外提供（供测试/监控使用） ==========================

    /**
     * 获取累计成功订单数
     * 用途：测试类验证"成功订单数 ≤ 最大库存"（确认无超卖）
     */
    public int getSuccessOrderCount() {
        return successOrderCount.get();
    }

    /**
     * 获取当前剩余库存
     * 用途：测试类验证"剩余库存 = 最大库存 - 成功订单数"（确认库存扣减准确性）
     */
    public int getRemainingStock() {
        return remainingStock.get();
    }

    /**
     * 判断库存是否已耗尽
     * 用途：测试类等待库存耗尽后，再开始最终统计（避免提前结束）
     */
    public boolean isStockExhausted() {
        return stockExhausted.get();
    }

    /**
     * 获取库存耗尽时间戳
     * 用途：测试类计算"秒杀完成耗时"（从启动到库存耗尽的时间）
     */
    public long getStockExhaustTime() {
        return stockExhaustTime;
    }
}
